package org.xteam.sled.semantic;

import java.util.ArrayList;
import java.util.List;

import org.xteam.sled.semantic.exp.Exp;
import org.xteam.sled.semantic.exp.ExpBindingInstance;
import org.xteam.sled.semantic.exp.ExpInstanceInput;
import org.xteam.sled.semantic.exp.ExpInstanceTagged;
import org.xteam.sled.semantic.exp.ExpRewriter;
import org.xteam.sled.semantic.exp.ExpTrue;
import org.xteam.sled.semantic.exp.ExpVar;

public class EliminateInstance extends ExpRewriter {
	
	@Override
	public void visitInstanceTagged(ExpInstanceTagged expInstanceTagged) {
		expInstanceTagged.getExpr().visit(this);
		Exp e = stack.pop();
		if (e instanceof ExpVar || e instanceof ExpInstanceInput) {
			stack.push(new ExpInstanceTagged(e, expInstanceTagged.getConstructor(), expInstanceTagged.getUid()));
		} else if (e instanceof ExpBindingInstance) {
			stack.push(new ExpTrue());
		} else {
			throw new RuntimeException();
//			 elim (U.INSTANCE_TAGGED (U.INSTANCE (_, cons'), {cons, uid})) =
//				        if cons = cons' then U.TRUE else U.FALSE
		}
	}

	@Override
	public void visitInstanceInput(ExpInstanceInput expInstanceInput) {
		expInstanceInput.getExpr().visit(this);
		stack.push(unwindInstanceInputs(stack.pop(), expInstanceInput.getConstructor(), expInstanceInput.getOperand()));
	}
	
	private static abstract class Result {
		public abstract Exp getExpr();
	}
	
	private static class BindingInstanceVar extends Result {
		String name;
		boolean wild;
		
		public BindingInstanceVar(String name, boolean wild) {
			this.name = name;
			this.wild = wild;
		}

		@Override
		public Exp getExpr() {
			if (wild) {
				// Wildcard.makeWild(name)
			}
			return new ExpVar(name);
		}
	}
	

	private static class Reduced extends Result {
		Exp exp;

		public Reduced(Exp exp) {
			this.exp = exp;
		}

		@Override
		public Exp getExpr() {
			return exp;
		}
	}
	
	private static class NoProgress extends Result {
		Exp exp;

		public NoProgress(Exp exp) {
			this.exp = exp;
		}

		@Override
		public Exp getExpr() {
			return exp;
		}
	}
	
	private Exp unwindInstanceInputs(Exp instance, InstructionConstructor cons, String operand) {
		return unwind(instance, cons, operand, new ArrayList<String>()).getExpr();
	}
	
	private Result unwind(Exp instance, InstructionConstructor cons, String operand, List<String> postfixName) {
		if (instance instanceof ExpVar) {
			return new NoProgress(new ExpInstanceInput(instance, cons, operand));
		}
		String dot = "<dot>";
		List<String> newname = new ArrayList<String>();
		newname.add(dot);
		newname.add(cons.name());
		newname.add(dot);
		newname.add(operand);
		newname.addAll(postfixName);
		if (instance instanceof ExpInstanceInput) {
			ExpInstanceInput ii = (ExpInstanceInput)instance;
			Result r = unwind(ii.getExpr(), ii.getConstructor(), ii.getOperand(), newname);
			if (r instanceof Reduced) {
				return unwind(((Reduced)r).exp, cons, operand, postfixName);
			}
			if (r instanceof NoProgress) {
				return new NoProgress(new ExpInstanceInput(((NoProgress)r).exp, cons, operand));
			}
			return r;
		}
		if (instance instanceof ExpBindingInstance) {
			ExpBindingInstance bi = (ExpBindingInstance) instance;
			OperandType ty = cons.getOperand(operand).getType();
			newname.add(0, bi.name());
			if (ty instanceof InstanceOperand) {
				return new Reduced(new ExpBindingInstance(concat(newname), true, ((InstanceOperand)ty).getConstructorType()));
			}
			return new BindingInstanceVar(concat(newname), bi.wild() ? true : newname.size() > 0);
		}
		throw new RuntimeException();
//		fun unwind ((instance, opspec as {cons, operand}), postfixName) =
//		  let val trueCons = C.fromId cons
//		      val dot = "<dot>"
//		      val newname = dot :: #name (C.info trueCons) :: dot :: operand :: postfixName
//		  in  case instance
//		        of U.INSTANCE (exps, cons' as {typeid, tag, family}) =>
//		             REDUCED (if cons = cons' then
//		                        C.operandSelect(trueCons, operand, exps)
//		                      else
//		                        Exp.fail "instance input from mismatched constructor")
//		         | U.BINDING_INSTANCE (name, wild, cons) =>
//		             (case #2 (C.operandNamed(trueCons, operand))
//		                of C.INSTANCE instype =>
//		                     REDUCED (U.BINDING_INSTANCE(String.concat(name :: newname),
//		                                                 true, instype))
//		                 | C.INTEGER _ =>
//		                     BINDING_INSTANCE_VAR (String.concat(name :: newname), 
//		                                           wild orelse not (null newname)))
//		         | U.INSTANCE_INPUT ? =>
//		             (case unwind(?, newname)
//		                of REDUCED e => unwind((e, opspec), postfixName)
//		                       (* guaranteed to terminate *)  (* was newname in old code *)
//		                 | NO_PROGRESS e => NO_PROGRESS (U.INSTANCE_INPUT(e, opspec))
//		                 | answer as BINDING_INSTANCE_VAR _ => answer)
//		         | _ => NO_PROGRESS (U.INSTANCE_INPUT (instance, opspec))
//		  end
	}

	private String concat(List<String> newname) {
		StringBuffer buffer = new StringBuffer();
		for (String name : newname) {
			buffer.append(name);
		}
		return buffer.toString();
	}
	
}
